#import "Controller.h"
#import	<CoreFoundation/CoreFoundation.h>
#import	<IOKit/IOKitLib.h>
#import	<IOKit/serial/IOSerialKeys.h>
#import <IOKit/usb/USB.h>

// http://www.google.com/codesearch?hl=en&q=+file:%5C.m%24+IOServiceAddMatchingNotification+show:DsnMHggYSgY:YZhCFkyX13Q:ri0ZHRr7L6k&sa=N&cd=1&ct=rc&cs_p=http://prdownloads.sourceforge.net/macaudiox/Max-0.7.1-source.tar.bz2&cs_f=Max-0.7.1-source/Frameworks/Growl/Extras/HardwareGrowler/USBNotifier.m#a0

#pragma mark C-style callbacks converted into Cocoa

static void deviceAddedNotification(void *refCon, io_iterator_t iter) 
{
	[(Controller *)refCon deviceAddedNotification:iter];
}

static void deviceRemovedNotification(void *refCon, io_iterator_t iter) 
{
	[(Controller *)refCon deviceRemovedNotification:iter];
}

@implementation Controller

-(id) init
{
	if( self = [super init] )
	{
		deviceList = [NSMutableArray new];
	}
	
	return( self );
}

-(void) dealloc
{
	if( deviceList != nil )
		[deviceList release];
		
	[super dealloc];
}

-(void) applicationDidFinishLaunching:(NSNotification *)note
{
	NSMutableDictionary *dictMatchAdded = (NSMutableDictionary *)IOServiceMatching( kIOSerialBSDServiceValue );

	[dictMatchAdded setValue:(NSString *)CFSTR(kIOSerialBSDRS232Type) forKey:(NSString *)CFSTR(kIOSerialBSDTypeKey)];

	NSMutableDictionary *dictMatchRemoved = [dictMatchAdded copy];

	mach_port_t masterPort;
	kern_return_t kr;
	CFRunLoopSourceRef runLoopSource;

	kr = IOMasterPort( MACH_PORT_NULL, &masterPort );
	if( !kr && masterPort ) 
	{
		IONotificationPortRef notifyPort = IONotificationPortCreate( masterPort );
		runLoopSource = IONotificationPortGetRunLoopSource( notifyPort );
		io_iterator_t deviceAddedIterator;
		io_iterator_t deviceRemovedIterator;

		CFRunLoopAddSource( [[NSRunLoop currentRunLoop] getCFRunLoop],
							runLoopSource,
							kCFRunLoopDefaultMode );

		// Notifications for added devices
		kr = IOServiceAddMatchingNotification( notifyPort,
											   kIOPublishNotification,
											   (CFDictionaryRef)dictMatchAdded,
											   deviceAddedNotification,
											   self,
											   &deviceAddedIterator );

		// Process added devices and 'prime' the notifications
		[self deviceAddedNotification:deviceAddedIterator];

		// Notifications for removed devices
		kr = IOServiceAddMatchingNotification( notifyPort,
											   kIOTerminatedNotification,
											   (CFDictionaryRef)dictMatchRemoved,
											   deviceRemovedNotification,
											   self,
											   &deviceRemovedIterator );
		
		[self deviceRemovedNotification:deviceRemovedIterator];

		mach_port_deallocate( mach_task_self(), masterPort );
	}
}

-(void) deviceAddedNotification:(io_iterator_t) dIter
{
	io_object_t obj;

	NSMutableArray *currentList = [self deviceList];
	
	while( obj = IOIteratorNext( dIter ) )
	{	
		NSString *usbVendorName;
		NSString *usbProductName;
		NSString *usbAddress;
		NSString *ttyDevice;
	
		usbVendorName = (NSString *)IORegistryEntrySearchCFProperty( obj,
																	 kIOServicePlane,
																	 CFSTR( "USB Vendor Name" ),
																	 nil,
																	 (kIORegistryIterateRecursively | kIORegistryIterateParents) );

		usbProductName = (NSString *)IORegistryEntrySearchCFProperty( obj,
																	 kIOServicePlane,
																	 CFSTR( "USB Product Name" ),
																	 nil,
																	 (kIORegistryIterateRecursively | kIORegistryIterateParents) );

		usbAddress = (NSString *)IORegistryEntrySearchCFProperty( obj,
																	 kIOServicePlane,
																	 CFSTR( kUSBDevicePropertyAddress ),
																	 nil,
																	 (kIORegistryIterateRecursively | kIORegistryIterateParents) );

		if( !usbVendorName )
			usbVendorName = @"Not a USB Device";
		
		if( !usbProductName )
			usbProductName = @"Not a USB Device";

		if( !usbAddress )
			usbAddress = @"Not a USB Device";
		
		ttyDevice = [self getRegistryStringForObject:obj withProperty:kIODialinDeviceKey];
		
		NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:ttyDevice, @"ttyDevice", 
																		usbVendorName, @"usbVendorName",
																		usbProductName, @"usbProductName",
																		usbAddress, @"usbAddress",
																		nil];
																		
		[currentList addObject:dict];
	}

	[self setDeviceList:currentList];
}

-(void) deviceRemovedNotification:(io_iterator_t) dIter
{
	io_object_t obj;

	NSMutableArray *currentList = [self deviceList];
	
	while( obj = IOIteratorNext( dIter ) )
	{
		NSString *ttyDevice = [self getRegistryStringForObject:obj withProperty:kIODialinDeviceKey];
		NSEnumerator *listEnum = [currentList objectEnumerator];
		id obj;
		
		while( obj = [listEnum nextObject] )
		{
			if( [[obj valueForKey:@"ttyDevice"] isEqualToString:ttyDevice] )
			{
				[currentList removeObject:obj];
			}
		}
	}

	[self setDeviceList:currentList];
}

-(NSString *) getRegistryStringForObject:(io_object_t)sObj withProperty:(char *)propName
{
    static char resultStr[256];
	CFStringRef propCFstring;
    CFTypeRef	nameCFstring;
    resultStr[0] = 0;
	
	// CFStringCreateWithCString returns a malloc'd result.  Cannot embed it into IORegistryEntryCreateCFProperty,
	// otherwise it leaks
	propCFstring = CFStringCreateWithCString(kCFAllocatorDefault, propName, kCFStringEncodingASCII);
    nameCFstring = IORegistryEntryCreateCFProperty(sObj, propCFstring, kCFAllocatorDefault, 0);

	CFRelease( propCFstring );
	
    if (nameCFstring)
    {
        CFStringGetCString( nameCFstring, resultStr, sizeof(resultStr), kCFStringEncodingASCII );
        CFRelease( nameCFstring );
    }
	
    return( [NSString stringWithCString:resultStr encoding:NSUTF8StringEncoding] );
}

-(NSMutableArray *) deviceList
{
	return( deviceList );
}

-(void) setDeviceList:(NSMutableArray *)newDevList
{
	[newDevList retain];
	
	if( deviceList != nil )
		[deviceList release];
		
	deviceList = newDevList;
}

@end
